package com.softwareengineering.trafficapplicationv2;

/**
 * This file is part of the Traffic Simulation Application.
 * The Traffic Simulation Application is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * The Traffic Simulation Application is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with the Traffic Simulation Application.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * @file OnRamp.java
 * @author (c) Martin Treiber, http://traffic-simulation.de/
 * @alterations David Black, Chris Nelson, and Garrett Stibbs, (c) December 03, 2012
 * 
 */

public class OnRamp extends MicroStreet {

	private double rampLength;
	private LaneChange laneChangeModel;
	private double mergingPos;
	private MicroStreet mainroad;
	private Moveable me, frontVeh; // on on-ramp
	private Moveable frontVehMain, backVehMain; // on main road
	private int i_insertmain;

	@SuppressWarnings("unchecked")
	public OnRamp(MicroStreet mainroad, double totalLength, 
			double rampLength, double mergingPosAtMainroad, 
			double p_factorRamp, double deltaBRamp){

		super(totalLength, 0., p_factorRamp, deltaBRamp, 0, ON_RAMP, null);
		laneChangeModel = new LaneChange(p_factorRamp, deltaBRamp);

		this.mainroad = mainroad;
		this.mergingPos = mergingPosAtMainroad;
		this.rampLength = rampLength;

		street.addElement(new Obstacle(roadLength, 0, 1., 0));

		// start with one car at the beginning
		street.addElement
		(new Car(5., idmCar.Veq(roadLength), 0, idmCar, inconsiderate, 
				PKW_LENGTH_M, colorCar, 2));
	}

	public void update(double time, double dt, double qIn, double perTr,
			double p_factorRamp, double deltaBRamp){
		this.time=time;

		laneChangeModel.set_p(p_factorRamp);
		laneChangeModel.set_db(deltaBRamp);

		old_pos=setPos();	// need old info for detectors and drawing

		accelerate(dt);
		translate(dt, choice_Szen);

		// no sink/source between determining positions and old pos etc!
		positions=setPos();
		velocities=setVel();
		colors=setColors();
		lengths=setLengths();

		inFlow(dt, qIn, perTr); 
		mergeToMainRoad(laneChangeModel, mergingPos); 
	}


	protected void accelerate(double dt){
		int imax=street.size()-1;
		
		for (int i=imax; i>=1; i--){
			Moveable me = (Moveable)street.elementAt(i);
			Moveable frontVeh = (Moveable)street.elementAt(i-1);
			me.accelerate(dt,  frontVeh);
		}
	}


	@SuppressWarnings("unchecked")
	private void mergeToMainRoad(LaneChange laneChangeModel,
			double mergingPos){
		final double offsetMain =  mergingPos - (roadLength-0.5*rampLength);
		int imax=street.size()-1;

		if(imax>=1) {  // at least one real vehicle on the ramp lane
			for (int i=1; i<=imax; i++){
				me = (Car)(street.elementAt(i)); 
				double x = me.position();

				if (x > roadLength - rampLength) { // action only in merging region
					frontVeh = (Moveable)street.elementAt(i-1);
					setNeighboursOnMainRoad (i, offsetMain); 
					// -> virtual vehicles frontVehMain, backVehMainfRamp, 
					// position i_insertmain
					//  positions are given in the ramp system!

					// do actual change if incentive criterion fulfilled
					me.setLaneChange(laneChangeModel); // update lane-change params
					if (me.change (frontVeh, frontVehMain, backVehMain)) {
						me.setLane(1); // right lane on future main road
						me.setLaneLast(2); // to allow smooth change from Right to right lane!
						me.setPosition(me.position()+offsetMain);
						me.setLaneChange(mainroad.inconsiderate);
						mainroad.street.insertElementAt(me, i_insertmain);
						street.removeElementAt(i);
						imax--;
					}
				}
			}
		}
	}

	private void setNeighboursOnMainRoad(int i, double offsetMain){
		// -> virtual vehicles frontVehMain, backVehMainfRamp
		// whose positions are given in the ramp system, and index
		// i_insertmain at which car is considered to be placed on main road
		final double farDistance=10000+ offsetMain;
		double x = ((Moveable)(street.elementAt(i))).position();
		int nvehmain = mainroad.street.size();
		int imain = 0;
		double xmain = farDistance;
		int lanemain;
		int i_frontmain;
		int i_backmain;

		if (nvehmain >0) {
			// determine index of last vehicle in front of ramp vehicle
			// on main road (regardless of lane!)
			for (imain=0; ((imain<nvehmain) && (x<xmain)); imain++) xmain = ((Moveable)(mainroad.street.elementAt(imain))).position() - offsetMain;

			i_frontmain=imain-1; //!!! imain-2
			i_backmain=imain-0;   //!!! imain-1
			i_insertmain=imain-0; //!!! imain-1

			// determine front vehicle on right (sic!) main lane (right=0)
			// if no vheicle(s), i_frontmain=-1 and/or i_backmain=nvehmain
			lanemain = (i_frontmain>=0) 
					? ((Moveable)(mainroad.street.elementAt(i_frontmain))).lane()
							: 1;

			while ((i_frontmain>=0) && (lanemain==0)){
				i_frontmain--;
				lanemain = (i_frontmain>=0) 
						? ((Moveable)(mainroad.street.elementAt(i_frontmain))).lane()
						: 1;
			}

			// determine back vehicle on right (sic!) main lane (right=0)
			lanemain = (i_backmain<nvehmain) 
					? ((Moveable)(mainroad.street.elementAt(i_backmain))).lane()
					: 1;

			while ((i_backmain<nvehmain) && (lanemain==0)){
				i_backmain++;
				lanemain = (i_backmain < nvehmain)
						? ((Moveable)(mainroad.street.elementAt(i_backmain))).lane()
						: 1;
			}
		}
		else {
			i_frontmain = -1;
			i_backmain  = -1;
		}

		// define virtual cars.
		// must copy cars (new ...) because otherwise (pointer assignment)
		// offset action below would offset streets on main road!
		frontVehMain = (i_frontmain<0) // only back vehicle(s)
				? new Car(farDistance, 0, 0, 
						idmCar, inconsiderate, 5, colorCar, 0)
		: new Car( (Car)(mainroad.street.elementAt(i_frontmain)));

		backVehMain = ((nvehmain<1)||(i_backmain >= nvehmain)) 
				? new Car(-farDistance, 0, 0,  // only front veh(s) or none
						idmCar, inconsiderate, 5, colorCar, 0)
		: new Car( (Car)(mainroad.street.elementAt(i_backmain)));
		// adjust positions to ramp system
		frontVehMain.setPosition(frontVehMain.position()-offsetMain);
		backVehMain.setPosition(backVehMain.position()-offsetMain);
	}

	@SuppressWarnings("unchecked")
	private void inFlow(double dt, double qIn, double perTr){
		final double spaceMin=27; // minimum headway for new vehicle
		int lane=0;
		double space=0;
		nin=nin+qIn*dt;

		if (nin>1.0) {  // new vehicle imposed by the inflow BC
			nin -= 1.0;
			int iPrev = street.size()-1;
			if (iPrev >= 0) space = ((Moveable)(street.elementAt(iPrev))).position();
			else space = roadLength;

			// enough space for new vehicle to enter? (!red)
			if (!(red=(space<spaceMin))){
				double     rand      = random.nextDouble()*1.0;
				int        randInt   = Math.abs(random.nextInt());
				MicroModel modelNew  = (rand<perTr) ? idmTruck : idmCar;
				double     vNew      = modelNew.Veq(space);
				double     lNew      = (rand<perTr) ? LKW_LENGTH_M : PKW_LENGTH_M;
				int      colorNew  = (rand<perTr) ? colorTruck : colorCar;
				
				street.insertElementAt
				((new Car(0.0, vNew, lane, modelNew, inconsiderate, 
						lNew, colorNew, randInt)), street.size());
			}
		}
	}
}
